#pragma once
#include <iostream>
#include <fstream>
#include <mutex>
#include <cassert>
// #include "oj_model.hpp"
#include "oj_mysql_model.hpp"
#include "oj_view.hpp"
#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include "../comm/httplib.h"
namespace ns_control
{
    using namespace std;
    using namespace ns_log;
    using namespace ns_util;
    using namespace ns_model;
    using namespace ns_view;
    using namespace httplib;

    class Machine
    {
        // 这里可以封装，但鉴于功能影响不大，则进行公开
    public:
        string _ip;
        int _port;
        uint64_t _load;
        std::mutex *_mutex;

    public:
        void icrLoad()
        {
            _mutex->lock();
            _load++;
            _mutex->unlock();
        }
        void decLoad()
        {
            _mutex->lock();
            _load--;
            _mutex->unlock();
        }
        void resetLoad()
        {
            _mutex->lock();
            _load = 0;
            _mutex->unlock();
        }
        uint64_t load()
        {
            uint64_t temp_load = 0;
            _mutex->lock();
            temp_load = _load;
            _mutex->unlock();
            return temp_load;
        }
    };
    const string machine_path = "./conf/machine.conf";

    class LoadBlance
    {
    private:
        // 全部主机信息
        vector<Machine> _all_machines;
        // 已经上线的主机信息id
        vector<int> _online_ids;
        // 已经下线的主机信息id
        vector<int> _offline_ids;
        std::mutex _mtx;

    public:
        LoadBlance()
        {
            LOG(DEBUG)<<"加载主机文件========="<<"\n";
            assert(loadConf(machine_path));
        }

        bool loadConf(const string &conf_path)
        {
            ifstream in(conf_path);
            if (!in.is_open())
            {
                LOG(Level::FATAL) << "未能成功加载" << conf_path << "\n";
                return false;
            }

            string line;
            while (getline(in, line))
            {
                vector<string> token;
                StringUtil::splitString(line, &token, ":");
                if (token.size() != 2)
                {
                    LOG(Level::WARNING) << "切分 " << line << "失败" << "\n";
                    continue;
                }

                Machine m;
                m._ip = token[0];
                m._port = atoi(token[1].c_str());
                m._load = 0;
                m._mutex = new mutex();

                _online_ids.push_back(_all_machines.size());
                _all_machines.push_back(m);
            }

            in.close();
             LOG(DEBUG)<<"加载主机文件成功"<<"\n";
            return true;
        }

        bool smartSelect(int *id, Machine **m)
        {
            _mtx.lock();

            int online_num = _online_ids.size();
            if (online_num == 0)
            {
                LOG(Level::FATAL) << "后端主机全部离线，可能服务器崩溃" << "\n";
                return false;
            }
            // 进行轮询选择
            *id = _online_ids[0];
            *m = &_all_machines[_online_ids[0]];
            uint64_t min_load = _all_machines[_online_ids[0]].load();
            for (int i = 1; i < online_num; i++)
            {
                uint64_t cur_load = _all_machines[_online_ids[i]].load();
                if (cur_load < min_load)
                {
                    *id = _online_ids[i];
                    *m = &_all_machines[_online_ids[i]];
                    min_load = cur_load;
                }
            }
            _mtx.unlock();
            return true;
        }
        void onlineMachine() 
        {
            LOG(DEBUG)<<"进行上线服务!"<<"\n";
            _mtx.lock();
            LOG(DEBUG)<<"获取互斥锁"<<"\n";
            _online_ids.insert(_online_ids.end(),_offline_ids.begin(),_offline_ids.end());
            _offline_ids.clear();
            _mtx.unlock();
            LOG(INFO)<<"所有主机已全部上线!"<<"\n";
        }
        void offlineMachine(int id)
        {
            _mtx.lock();
            for (auto it = _online_ids.begin(); it != _offline_ids.end(); it++)
            {
                if (*it == id)
                {
                    _all_machines[id].resetLoad();
                    // 要离线的主机已经找到啦
                    _online_ids.erase(it);
                    _offline_ids.push_back(*it);
                    break; // 因为break的存在，所有我们暂时不考虑迭代器失效的问题
                }
            }
            _mtx.unlock();
        }
        void showMachine() {}

        ~LoadBlance()
        {
        }
    };

    class Control
    {
    private:
        Model _model;           // 后台数据管理
        View _view;             // 网页渲染
        LoadBlance _loadblance; // 负载均衡控制器
    public:
        Control() {}
        ~Control() {}

    public:
        bool allQuestion(string *html)
        {
            // 获取所有题目并进行渲染
            vector<Question> questions;
            if (!_model.getAllQuestions(&questions))
            {
                *html = "获取题目失败, 形成题目列表失败";
                return false;
            }
            sort(questions.begin(), questions.end(), [](const struct Question &q1, const struct Question &q2)
                 { return atoi(q1.number.c_str()) < atoi(q2.number.c_str()); });
            // 获取题目信息成功，将所有的题目数据构建成网页
            _view.expandAllQuestions(questions, html);
            return true;
        }
        bool question(const string &number, string *html)
        {
            Question question;
            if (!_model.getOneQuestion(number, &question))
            {
                *html = "获取" + number + "题目失败，请检查数据库";
                return false;
            }
            // 获取指定题目信息成功，将所有的题目数据构建成网页
            _view.expandOneQuestion(question, html);
            return true;
        }

        void judge(const string &number, const string &in_json, string *out_json)
        {
            // 1.根据题号，获取题目细节
            struct Question q;
            _model.getOneQuestion(number, &q);
            // 2.将in_json反序列化，拿到用户代码和输入
            Json::Value in, compile;
            JsonUtil::DeSerialize(in_json, in);
            string code = in["code"].asString();

            // 3.拼接用户代码，获取其他细节生成新的json串
            compile["input"] = in["input"].asString();
            compile["code"] = code + "\n" + q.test;
            compile["cpu_limit"] = q.cpu_limit;
            compile["mem_limit"] = q.mem_limit;
            string complie_string;
            JsonUtil::Serialize(compile, complie_string);

            // 4.选择负载最小的主机
            while (true)
            {
                int id = 0;
                Machine *m;
                if (!_loadblance.smartSelect(&id, &m))
                {
                    break;
                }
                // 5.进行http请求直到获取结果
                Client client(m->_ip, m->_port);
                m->icrLoad();
                LOG(Level::INFO) << "连接主机 " << id << "成功详情: " << m->_ip << ":" << m->_port
                                 << " 当前主机的负载是: " << m->load() << "\n";
                if (auto resp = client.Post("/compile_run", complie_string, "application/json;charset=utf-8"))
                {
                    if (resp->status == 200)
                    {
                        // 6.进行回复获取
                        *out_json = resp->body;
                        m->decLoad();
                        LOG(INFO) << "请求编译和运行服务成功..." << "\n";
                        break;
                    }
                    m->decLoad();
                }
                else
                {
                    // 请求失败
                    LOG(ERROR) << " 当前请求的主机id: " << id << " 详情: " << m->_ip << ":" << m->_port << " 可能已经离线" << "\n";
                    _loadblance.offlineMachine(id);
                    _loadblance.showMachine(); // 仅仅是为了用来调试
                }
            }
        }
        void recoveryMachine()
        {
            LOG(DEBUG)<<"进行上线服务!"<<"\n";
            _loadblance.onlineMachine();
        }
    };
}